/**
 * @file wvf-dist.ts 此文件编译后嵌入 index.html 中的 <head><script defer>wvf-dist.js file contents </script> </head>
 */

/**
 * 响应式数据 参考 https://cn.vuejs.org/guide/essentials/reactivity-fundamentals
 * 在响应式数据被 修改之后，触发 Proxy::set 调用，Proxy::sset 会遍历 标签实例下的 ReactiveData::_vars 的函数
 */
interface ReactiveData  {
    /**
     * 响应式数据
     */
    _vars:Function[]
    /**
     * 响应式标签对象的 data-w 的 属性，应为 number
     */
    [data_w:number]:{
        /** 响应式标签  在 Text 的序号，注意：仅计算 Text。 比如<span>a<span></span>b</span> 上面 的 a 序号为0 b 序号为1*/
        ser:number,
        /**
         * 响应式标签 内部将会成生的 字符串
         */
        fun:Function,
    },
}
interface WVFData {
    [_className:string]:{
        ref:ReactiveData[],
    }
}
declare var WVF:WVFData;
// /** custom element intance 自定义元素实例 */
// var WVF:WVFData = {
//     HelloWorld: {
//         ref: [{
//                 _vars:[function(){this._data.count.a;}],
//                 0:{
//                     ser:0,
//                     fun:function(){return `Count is ${ Math.sqrt(this._data.count.a) }`},
//                 }
//             },
//         ],
//     },
//     About: {
//         ref: [{
//                 _vars:[function(){this._data.count;}],
//                 1:{
//                     ser:0,
//                     fun:function(){return `Count is ${ this._data.count }`},
//                 }
//             },
//         ],
//     }
// }
/**
 * 定义 WVF 全局数据
 */
// declare var WVF:{[_className:string]:WVFClass};
// import {type DocumentEventMap} from "typescript/lib/lib.dom.d.ts"
/**
 * 页面中的每一个wvf元素的ID
 */
var ID:bigint = BigInt(1);

/**
 * 定义一个全局
 */
interface WVFClass{
    /**
     * 检查标签中含有的字符串中如果有 {{ }} 结构的 对象保存在此
     */
    text?:{
            _vars:(string|object)[]
            data_w:string, // 对应的标签所赋的 data-w 属性值
            ser:number, // 在标签中的序号
            fun:Function, // 最后生成的函数            
        }[],
}

/**
 * 页面中的 <div id="wvf-template"></div>， 所有 wvf 中的 <template name="xxx"><template>都会保存在此
 */

var TEMPLATE:HTMLTemplateElement = document.body.querySelector('#wvf-template') as any as HTMLTemplateElement;
// declare var window:Window & typeof globalThis;
// 声明一个函数在页面加载后运行
/**
 * 声明一个函数在页面加载后运行
 * @param listener 
 */
// function SO(l: (this: Document, ev: Event) => any){
//     document.addEventListener('DOMContentLoaded',l,{once:true});
// }

/**
 * wvf 组件的定义
 */
interface WvfComponent{
    name:string, // 类名
    data?:{[key:string]:any},
    methods:{[key:string]:Function},
    onInit?():void, // 当组件初始化时调用,此时还没有被添加到html中,所以不能添加元素
    onConnected?():void,// 当组件添加到文档中时调用
    onDisconnected?():void, // 当组件从文档中移除时调用
    onAdopted?():void, // 当组件被移动到新文档时调用,比如调用  document.adoptNode() 方法 使用 appendChild()、insertBefore() 或 replaceChild() 将元素从一个文档移动到另一个
    onAttributeChanged?(name:string, oldValue:string, newValue:string):void,// 当组件属性改变时调用,
}
/**
 * 检查一个对象是否像 Array 一样可索引
 * @param obj 
 * @returns 
 */
function isArrayLike(obj: any) {
    if (obj == null || typeof obj !== 'object') {  return false; }
    const length = obj.length;
    return typeof length === 'number';
}

/**
 * 根据 WvfComponent 格式的 component 创建一个 组件
 * @param component 
 * @returns 
 */
//@ts-ignore
function createComponent(component:WvfComponent):WvfComponent|void{
    if(!component.name){return;}
    let _ID = ID++;
    let tag = `w-${component.name.toLowerCase()}`;
    class C extends HTMLElement{
        /**
         * 类名（由于是从同一个类导出的，所以要设置此属性）
         */
        static _className:string = component.name; // 类名
        /**
         * 标签名，通过 类名 修改而来 `w-${this._className}`
         */
        static _tagName:string = tag; // 标签名
        public _ID:bigint; // 实例的 编号，唯一， 通过此 _ID 在 WVF 中查找
        public _EvId:number; //事件 ID 
        public _data:ProxyConstructor;  // Proxy 数据
        private _dataReactive:ReactiveData[];  // Proxy 关联的 {{ }} 结构对象
        private _dataReactiveMarking:[any,string,any]; //标记正在访问的响应式对象
        public _method:{[key:string]:Function};// 方法
        constructor(){
            super();
            // let c =this.removeChild,d = this.replaceChild;
            // this.removeChild =  <T extends Node>(child: T):T=>{
            //     if(Object.hasOwn(child,"_ID") && WVF.has((child as any)._ID)){
            //         WVF.delete((child as any)._ID);
            //     }
            //     return c(child);
            // }
            // this.replaceChild = <T extends Node>(node: Node, child: T): T=>{
            //     if(Object.hasOwn(child,"_ID") && WVF.has((child as any)._ID)){
            //         WVF.delete((child as any)._ID);
            //     }
            //     return d(node, child);  
            // }
            this._ID = _ID ;
            this._EvId=0;
            if(component.data){
                this._dataReactive = WVF[C._className]?.ref;
                this._dataReactiveMarking = [undefined,"",undefined];
                this._data = this._proxy(component.data); // 初始化 this._data
            }
            this._method = component.methods;
            if(component.onInit)component.onInit.call(this);
        }
        connectedCallback(){
            let e = TEMPLATE.querySelector(`[name=${component.name}`) as any as HTMLTemplateElement;
            if(e){this.append(e.content.cloneNode(true));}
            if(component.onConnected) component.onConnected.call(this);
            if(this._data){
                this._proxyInitData(this._data); //主动写一遍数据实现响应式界面的更新
            }
        }
        disconnectedCallback(){
            if(component.onDisconnected) component.onDisconnected.call(this);
        } 
        adoptedCallback(){
            if(component.onAdopted) component.onAdopted.call(this);
        }
        attributeChangedCallback(name:string, oldValue:string, newValue:string){
            if(component.onAttributeChanged) component.onAttributeChanged.call(this, name,oldValue,newValue );
        }
        private static ORG = [Number, String, Boolean, Symbol, BigInt,]; // 原始对象，不可使用 Proxy 代理
        /**
         * 检查是否是原始对象，即不可被 Proxy 的对象
         * @param vv 
         * @returns 
         */
        private static checkIfOriginalObject (vv:any) {
            return (vv === undefined || vv === null || C.ORG.includes(vv.constructor))
        }
        private _proxy(value:{[key:string]:any}):ProxyConstructor{
            /**
             * 将 [] 或 {} 对象转换为 Proxy
             * @param vv 
             * @returns 
             */
            const genProxy1 = (vv:any)=>{
                const proxy = new Proxy(vv,{
                    get:(target: any, property: string | symbol, receiver: any):any=>{
                        this._dataReactiveMarking = [target,property as string,receiver];
                        return target[property];
                    },
                    set:(target: any, property: string | symbol, newValue: any, receiver: any):boolean=>{
                        target[property] =newValue;
                        for(let i = 0; i != this._dataReactive.length; i++){
                            for(let j = 0; j != this._dataReactive[i]._vars.length; j++){
                                try{
                                    this._dataReactive[i]._vars[j].call(this);
                                } catch(e){
                                    continue;
                                }
                                if(this._dataReactiveMarking[2] == receiver && this._dataReactiveMarking[0] == target && this._dataReactiveMarking[1] == property){
                                    const {_vars,...w}  = this._dataReactive[i];
                                    for(let data_w in w){
                                        let e = this.querySelector("[data-w='" + data_w + "']") as HTMLElement; // "[data-w=\"0\"]"
                                        if(e) {
                                            let ser = 0;
                                            for(let i = 0; i != e.childNodes.length; i++){
                                                if(e.childNodes[i].nodeType == Node.TEXT_NODE){
                                                    if(ser < w[data_w].ser){
                                                        ser++;
                                                    }else{
                                                        (e.childNodes[i] as Text).data = w[data_w].fun.call(this);//直接修改 Text.data 为目录字符串
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    return true;
                                }
                            }
                        }
                        return true;
                    }
                });
                return proxy;
            }
            /**
             * 将 [] 或 {} 对象转换为 Proxy
             * @param vv 
             * @returns 
             */
            const genProxy2 = (vv:any)=>{
                const proxy = new Proxy(vv,{
                    get:(target: any, property: string | symbol, receiver: any):any=>{
                        this._dataReactiveMarking = [target,property as string,receiver];
                        return target[property];
                    },
                    set:(target: any, property: string | symbol, newValue: any, receiver: any):boolean=>{
                        target[property] =newValue;
                        for(let i = 0; i != this._dataReactive.length; i++){
                            for(let j = 0; j != this._dataReactive[i]._vars.length; j++){
                                try{
                                    this._dataReactive[i]._vars[j].call(this);
                                } catch(e){
                                    continue;
                                }
                                if(this._dataReactiveMarking[2] == receiver && this._dataReactiveMarking[0] == target && this._dataReactiveMarking[1] == property){
                                    const {_vars,...w}  = this._dataReactive[i];
                                    for(let data_w in w){
                                        let e = this.querySelector("[data-w='" + data_w + "']") as HTMLElement; // "[data-w=\"0\"]"
                                        if(e) {
                                            let ser = 0;
                                            for(let i = 0; i != e.childNodes.length; i++){
                                                if(e.childNodes[i].nodeType == Node.TEXT_NODE){
                                                    if(ser < w[data_w].ser){
                                                        ser++;
                                                    }else{
                                                        (e.childNodes[i] as Text).data = w[data_w].fun.call(this);//直接修改 Text.data 为目录字符串
                                                    }
                                                }
                                            }
                                        }
                                    }
                                    return true;
                                }
                            }
                        }
                        return true;
                    }
                });
                return proxy;
            }
            /**
             * 递归调用，把 data 下的数据全部变为 Proxy 
             * @param vv data 或 data 下的数据
             * @param stack 栈
             * @returns 
             */
            const toProxy =  (vv:any )=>{
                if(C.checkIfOriginalObject(vv)){
                    return vv;
                }
                switch(vv.constructor){
                    case Array:{
                        let proxy = genProxy1([]);
                        for(let i = 0; i != vv.length; i++){
                            proxy.push(toProxy(vv[i]));
                        }
                        return proxy;
                    }break;
                    case Object:{
                        let proxy = genProxy1({});
                        for(const k in vv){
                            proxy[k] = toProxy(vv[k]);
                        }
                        return proxy;
                    }break;
                    case Map:{
                        let proxy = genProxy1(new Map);
                        for(const [k,v] of vv as Map<any,any>){
                            proxy.set(k,toProxy(v));
                        }
                    }break;
                    // case Set:{

                    // }break;
                    default:
                        return genProxy1(vv);
                }
            }
            let r = toProxy(value); 
            return r;
        };
        private _proxyInitData(value:{[key:string]:any}):void{
            const recursion = (vv:any, stack:any[] = [], )=>{
                switch(vv.constructor){
                    case Array:{
                        for(let i = 0; i != vv.length; i++){
                            if(C.checkIfOriginalObject(vv)){
                                vv[i] = vv[i];
                            }else{
                                stack.push(i);
                                recursion(vv[i],stack);
                                stack.pop();
                            }
                        }
                    }break;
                    case Object:{
                        for(const k in vv){
                            if(C.checkIfOriginalObject(vv[k])){
                                vv[k] = vv[k];
                            }else{
                                stack.push(k);
                                recursion(vv[k], stack);
                                stack.pop();  
                            }
                        }
                    }break;
                    // case Map:{
                    //     for(const [k,v] of vv){
                    //         if(C.checkIfOriginalObject(v)){
                    //             vv.set(k,v);
                    //         }else{
                    //             stack.push(k);
                    //             recursion(v, stack);
                    //             stack.pop();  
                    //         }
                    //     }
                    // }break;
                    // case Set:{
                    //     let ser = 0;
                    //     for(const v of vv){
                    //         if(C.checkIfOriginalObject(v)){
                    //             vv.add(v);
                    //         }else{
                    //             stack.push(ser);
                    //             recursion(v, stack);
                    //             stack.pop(); 
                    //         }
                    //         ser++;
                    //     }
                    // }break;
                    default:
                        break;
                }
            }
            recursion(value);
        }
    }

    // for(let k in component){
    //     //@ts-ignore
    //     C[k] = component[k];
    // }
    window.customElements.define(tag,C); 
    return C as any as WvfComponent;
}

